package org.mbds.wolf.db;

import org.mbds.wolf.exception.UserException;
import org.mbds.wolf.exception.UserExceptionReason;

/**
 *
 * @author amles_000
 */
public class DataTable extends AtomicData {

    public final static short COLUMNS_ARRAY_SIZE_MAX = 256;
    public final static short COLUMNS_ARRAY_SIZE_INIT = 32;
    public final static short COLUMNS_ARRAY_SIZE_INC = 32;
    
    
    private short countColumns = 0;
    private DataColumn[] columns = new DataColumn[COLUMNS_ARRAY_SIZE_INIT];
    
    /**
     * Constructor
     * @param id
     * @param name
     */
    DataTable(short id, byte[] name) {
        super(id, name);
    }
    
    /**
     * countColumns
     * @return count columns
     */
    short countColumns() {
        return countColumns;
    }

    /**
     * addColumn
     * @param id
     * @param name
     * @throws UserException 
     */
    void addColumn(final short id, final byte[] name) throws UserException {
        byte[] column = name;
//        byte[] column = null;
//        try {
//        //TODO: Column name should be char types without space only   
//            column = DataTool.trim(name);
//        } catch (CardRuntimeException e) {
// //           UserException.throwIt(e.getReason());
//        }
        boolean isFoundId = isColumnIdAlreadyUsed(id);
        boolean isFoundName = isColumnNameAlreadyUsed(column);
        if (isFoundId && isFoundName)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_ALREADY_EXIST);
        if (isFoundId)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_ID_ALREADY_USED);
        if (isFoundName)
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        if ((countColumns + 1 > columns.length) && !increaseColumnsArray(COLUMNS_ARRAY_SIZE_INC))
            UserException.throwIt(UserExceptionReason.ARRAY_INDEX_OUT_OF_BOUND);
        DataColumn col = new DataColumn(id, column);
        columns[countColumns] = col;
        countColumns++;
    }    

    /**
     * select
     * @param columnId
     * @param recordId
     * @return byte[] value if exists and not deleted
     */
    byte[] select(short columnId, short recordId) {
        DataColumn col = getDataColumnById(columnId);
        if (col==null || col.isDeleted) return null;
        return col.selectData(recordId);
    }
    
    /**
     * select
     * @param columnId
     * @param recordId
     * @return byte[] value if exists and not deleted
     */
    byte[] select(byte[] columnName, short recordId) throws UserException {
        DataColumn col = null;
        try {
            col = getDataColumnByName(columnName);
        } catch (UserException e) {
            throw e ;
        }
        if (col==null || col.isDeleted) return null;
        return col.selectData(recordId);
    }
    
    /**
     * insert
     * @param columnName
     * @param data
     * @return id or -1 if column not found
     * @throws UserException 
     */
    short insert(byte[] columnName, byte[] data) throws UserException {
        DataColumn col = getDataColumnByName(columnName);
        if (col==null) return -1;
        try {
            return col.insertData(data);
        } catch (UserException e) {
            throw e;
        }
    }
    /**
     * insert
     * @param columnId
     * @param data
     * @return id or -1 if column not found
     * @throws UserException 
     */
    short insert(short columnId, byte[] data) throws UserException {
        DataColumn col = getDataColumnById(columnId);
        if (col==null) return -1;
         try {
             return col.insertData(data);
        } catch (UserException e) {
            throw e;
        }      
    }

    /**
     * insert
     * @param columnId
     * @param dataArray
     * @return nb data inserted
     * @throws UserException 
     */
    short insert(short columnId, Object[] dataArray) throws UserException {
        DataColumn col = getDataColumnById(columnId);
        if (col==null) return 0;
        try {
            return col.insertData(dataArray);
        } catch (UserException e) {
            throw e;
        }
    }
    /**
     * insert
     * @param columnName
     * @param dataArray
     * @return nb data inserted
     * @throws UserException 
     */
    short insert(byte[] columnName, Object[] dataArray) throws UserException {
        DataColumn col = getDataColumnByName(columnName);
        if (col==null) return 0;
        try {
            return col.insertData(dataArray);
        } catch (UserException e) {
            throw e;
        }
    }
    /**
     * delete
     * @param columnName
     * @param recordId
     * @return nb record deleted (0..1)
     * @throws UserException 
     */
    short delete(byte[] columnName, short recordId) throws UserException {
        DataColumn col = null;
        try {
            col = getDataColumnByName(columnName);
        } catch (UserException e) {
            throw e;
        }
        if (col==null || col.isDeleted) return 0;
        return col.deleteData(recordId);
    }

    /**
     * delete
     * @param columnId
     * @param recordId
     * @return nb records deleted
     */
    short delete(short columnId, short recordId) {
        DataColumn col = getDataColumnById(columnId);
        if (col == null || col.isDeleted) {
            return 0;
        }
        return col.deleteData(recordId);
    }
    
    /**
     * selectColumnById
     * @param columnId
     * @return AtomicData[]
     */
    AtomicData[] select(short columnId) {
        DataColumn col = getDataColumnById(columnId);
        if (col==null) return null;
        return col.selectData();
    } 
    /**
     * selectColumnByName
     * @param columnName
     * @return AtomicData[]
     */
    AtomicData[] select(byte[] columnName) throws UserException {
        DataColumn col = null;
        try {
            col = getDataColumnByName(columnName);
        } catch (UserException e) {
            throw e;
        }
        if (col==null) return null;
        return col.selectData();
    } 

    /**
     * renameColumn
     * @param column id 
     * @param new column name 
     * @throws UserException 
     */
    void renameColumn(short id, byte[] newName) throws UserException {
        short idx = DataTool.containsId(id, columns);
        if (idx < 0) {
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        }
        if (isColumnNameAlreadyUsed(newName))
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        columns[idx].rename(newName);
    }   
    /**
     * renameColumn
     * @param column name 
     * @param new column name 
     * @throws UserException 
     */
    void renameColumn(byte[] columnName, byte[] newName) throws UserException {
        DataColumn col = getDataColumnByName(columnName);
        if (col==null) {
            UserException.throwIt(UserExceptionReason.INVALID_ARGUMENT_EXCEPTION);
        }
        if (isColumnNameAlreadyUsed(newName))
            UserException.throwIt(UserExceptionReason.DB_ATOMIC_DATA_NAME_ALREADY_USED);
        col.rename(newName);
    }   
    
    /**
     * drop
     * @return boolean
     */
    boolean drop() {
        if (isDeleted) return false;
        for (short i=0;i<columns.length;i++)
            if (columns[i]!=null) 
                columns[i].drop();
        isDeleted = true;
        return true;
    }
    
    /**
     * getDataColumnByName
     * @param name
     * @return DataColumn
     * @throws UserException 
     */
    private DataColumn getDataColumnByName(byte[] name) throws UserException {
        short idx = -1;
        try {
            idx = DataTool.containsValue(name, columns);
        } catch (UserException e) {
            throw e;
        }
        if (idx<0) return null;
        return columns[idx];
    }
    /**
     * getDataColumnById
     * @param id
     * @return DataColumn
     */
    private DataColumn getDataColumnById(short id) {
        short idx = DataTool.containsId(id, columns);
        if (idx<0) return null;
        return columns[idx];
    }

    /**
     * dropColumnById
     * @param colId
     * @return boolean
     */
    boolean dropColumnById(short colId) {
        DataColumn col = getDataColumnById(id);
        if (col==null || col.isDeleted()) return false;
        col.drop();
        return true;
    }

    /**
     * update
     * @param columnId
     * @param recordId
     * @param newValue
     * @return nb records updated
     */
    short updateData(short columnId, short recordId, byte[] newValue) {
        DataColumn col = getDataColumnById(columnId);
        if (col==null || col.isDeleted) return 0;
        return col.updateData(recordId, newValue);
    }    
    
    /**
     * update
     * @param columnName
     * @param recordId
     * @param newValue
     * @return nb records updated
     * @throws UserException 
     */
    short updateData(byte[] columnName, short recordId, byte[] newValue) throws UserException {
        DataColumn col = null;
        try {
            col = getDataColumnByName(columnName);
        } catch (UserException e) {
            throw e;
        }
        if (col==null || col.isDeleted) return 0;
        return col.updateData(recordId, newValue);    
    }

    /**
     * dropColumnByName
     * @param colName
     * @return boolean
     * @throws UserException 
     */
    boolean dropColumnByName(byte[] colName) throws UserException {
        DataColumn col = null;
        try {
            col = getDataColumnByName(colName);
        } catch (UserException e) {
            throw e;
        }
        if (col==null || col.isDeleted()) return false;
        col.drop();
        return true;
    }
    
    /**
     * isColumnIdAlreadyUsed
     * @param id
     * @return boolean
     */
    boolean isColumnIdAlreadyUsed(short id) {
        return (DataTool.containsId(id, columns)!=-1);
    }
    /**
     * isColumnNameAlreadyUsed
     * @param id
     * @return boolean
     */
    boolean isColumnNameAlreadyUsed(final byte[] name) throws UserException {
        try {
            return (DataTool.containsValue(name, columns)!=-1);
        } catch (UserException e) {
            throw e;
        }
    }
    /**
     * increaseColumnsArray
     * @param inc
     * @return boolean
     */
    private boolean increaseColumnsArray(short inc) {
        if (columns.length + inc > COLUMNS_ARRAY_SIZE_MAX) {
            return false;
        }
        DataColumn[] newColums = new DataColumn[(short) (columns.length + inc)];
        for (short i = 0; i < columns.length; i++) {
            newColums[i] = columns[i];
        }
        columns = newColums;
        return true;
    }
    /**
     * name
     * @return column name
     */
    byte[] name() {
        return value();
    }
    /**
     * rename
     * @param new column name 
     */
    void rename(byte[] name) throws UserException {
        this.value = name;
    }
}
